AWS CDKでAWS Step FunctionsのCI/CD環境を作ってみた
AWS CDKで何でも作ってみたい
こんにちは、のんピ(@non____97)です。
皆さんは何でもAWS CDKでゴリ押しますか? 私は何でもゴリ押します。
今回、AWS Step FunctionsのCI/CD環境を作る機会があったので、AWS CDKの底力を知るべくAWS CDKでチャレンジしてみました。
作成したAWS CDKのリポジトリは以下になります。
こちらの記事は、今までの私のAWS Step FunctionsやCodeCommit関連の記事の総集編のような意味合いが強いです。自身で試したり、中身を知りたい方は以下記事も参照しながらご覧ください。
構成図
上述のリポジトリでAWS CDKを使ってデプロイされるリソースの構成図は以下の通りです。
想定している使い方
想定している使い方は以下の通りです。
- インフラチームはSlackで各種イベント発生時の通知で使用するIncoming Webhooksアプリを作成する
- アプリチームはステートマシンを作成する際、インフラチームに以下情報を伝える
- 作成するステートマシンの名前
- デプロイ先のAWSアカウント
- Pull Requestやステートマシンの実行結果などを通知するSlackチャンネル
- インフラチームはIncoming Webhooksアプリの設定で、アプリチームから受け取ったSlackチャンネルへの投稿を許可する
- インフラチームは受け取った情報を元にAWS CDKで各種リソースをデプロイする
- 同じアカウントに複数ステートマシンを作りたい場合は、
CicdStack
を複数デプロイする
- 同じアカウントに複数ステートマシンを作りたい場合は、
- アプリチームは作成されたCodeCommitリポジトリで
develop
ブランチ、feature
ブランチを作成する - アプリチームはAWS Step Functions Workflow Studioでステートマシンのワークフローを設計し、生成された定義(ASL形式)を控えておく
- アプリチームは以下のような情報を記載したファイルを
feature
ブランチにpushする- ステートマシンの説明
- ASL形式で記述されたステートマシンのワークフロー
- ステートマシンが起動するためのCron式
- ステートマシンが起動するためのイベントパターン
- AWS X-Ray でトレースを有効にするか
- ステートマシンのIAMロールにアタッチされるIAMポリシーのドキュメント
- アプリチームは
feature
ブランチからdevelop
ブランチへのPull Requestを作成する - アプリチームのマネージャーはPull Requestの内容を確認し、承認及び、
develop
ブランチにマージする - アプリチームのマネージャーは
develop
ブランチからmain
ブランチへのPull Requestを作成する - インフラチームはPull Requestの内容を確認し、承認及び、
main
ブランチにマージする main
ブランチが更新されたことをトリガーにCodeBuildが動き、AWS SAMでステートマシンやEventBridgeルールなどをデプロイする
なお、「あれ、AWS CDKじゃなくて、AWS SAM使ってステートマシンをデプロイしているじゃん」と思われた方もいるかも知れません。AWS SAMを使った理由は以下記事で紹介されている通り、AWS SAMではASL形式で記述されたステートマシンのワークフローをそのまま利用できるためです。
これにより、AWS Step Functions Workflow Studioで設計したステートマシンのワークフローを流用することが出来ます。
一方で、AWS CDKにはそのような機能は現時点では提供されていません。仮にAWS CDKでステートマシンをデプロイしようとすると、AWS Step Functions Workflow Studioで生成された定義からコードに落とすという作業が必要になります。
やってみた
Incoming Webhooksアプリの作成
それでは早速やってみます。
まず、Incoming Webhooksアプリの作成です。こちらの手順については、以前に以下の記事で紹介しているので割愛します。
アプリチームからインフラチームにCI/CD環境に必要な情報を提供
次に、AWS Step FunctionsのCI/CD環境を作成するにあたって、アプリチームから以下必要な情報を共有してもらいます。
- 作成するステートマシンの名前
- デプロイ先のAWSアカウント
- Pull Requestやステートマシンの実行結果などを通知するSlackチャンネル
Incoming Webhooksアプリの設定で、Slackチャンネルへの投稿を許可
次に、Incoming Webhooksアプリの設定で、先の手順で連絡を受けたSlackチャンネルに対して投稿を許可します。
こちらの手順についても、以前に以下の記事で紹介しているので割愛します。
AWS CDKで各種リソースをデプロイ
インフラチームは受け取った情報を元にAWS CDKで各種リソースをデプロイします。
まず、リポジトリのクローンや、必要なモジュールのインストール、スイッチロール元のAWSアカウントなどの設定ファイルの作成などを行います。
# リポジトリのクローン $ git clone https://github.com/non-97/cicd-aws-step-functions.git $ cd cicd-aws-step-functions # 必要なモジュールをインストール $ npm install # スイッチロール元のAWSアカウントや、通知に使うWebhook URL、イベント連携元のAWSアカウントを入力 $ vi .env $ cat .env # .env file # # Add environment-specific variables on new lines in the form of NAME=VALUE # JUMP_ACCOUNT=xxx APP_TEAM_WEBHOOK_URL=https://hooks.slack.com/services/xxx APP_TEAM_MANAGER_WEBHOOK_URL=https://hooks.slack.com/services/yyy INFRA_TEAM_WEBHOOK_URL=https://hooks.slack.com/services/zzz SOURCE_ACCOUNTS=["yyy"]
次にステートマシンの名前を設定します。
./bin/cicd-aws-step-functions.ts
のnew CicdStack
の箇所でステートマシン名及び、そのステートマシンに関するCI/CD環境と分かるようにスタック名を設定します。
以下例では、ステートマシン名はStateMachineTest001
としています。
#!/usr/bin/env node import "source-map-support/register"; import "dotenv/config"; import * as cdk from "aws-cdk-lib"; import { SfnTemplateBucketStack } from "../lib/sfn-template-bucket-stack"; import { ArtifactBucketStack } from "../lib/artifact-bucket-stack"; import { RoleStack } from "../lib/role-stack"; import { EventBusStack } from "../lib/event-bus-stack"; import { NoticeSfnCicdEventsFunctionStack } from "../lib/notice-sfn-cicd-events-function-stack"; import { WorkflowSupportFunctionStack } from "../lib/workflow-support-function-stack"; import { CicdStack } from "../lib/cicd-stack"; import { Ec2InstancesStack } from "../lib/ec2-instances-stack"; const app = new cdk.App(); // If the variable specified by dotenv is not defined, the process is aborted if ( typeof process.env.APP_TEAM_WEBHOOK_URL == "undefined" || typeof process.env.APP_TEAM_MANAGER_WEBHOOK_URL == "undefined" || typeof process.env.INFRA_TEAM_WEBHOOK_URL == "undefined" || typeof process.env.JUMP_ACCOUNT == "undefined" ) { console.error(` There is not enough input in the .env file. Please enter a value in the .env file.`); process.exit(1); } // Define a webhook URL in dotenv to notify each channel const appTeamWebhookUrl = process.env.APP_TEAM_WEBHOOK_URL; const appTeamManagerWebhookUrl = process.env.APP_TEAM_MANAGER_WEBHOOK_URL; const infraTeamWebhookUrl = process.env.INFRA_TEAM_WEBHOOK_URL; // Stack of S3 buckets to store AWS Step Function template files and CodeBuild shell scripts const sfnTemplateBucketStack = new SfnTemplateBucketStack( app, "SfnTemplateBucketStack" ); // Stack of S3 buckets for CodeBuild artifacts const artifactBucketStack = new ArtifactBucketStack(app, "ArtifactBucketStack"); // Stack of IAM roles and CodeCommit approval rule templates for each role const roleStack = new RoleStack(app, "RoleStack", { jumpAccount: process.env.JUMP_ACCOUNT, }); // Stack of Event Bus // It is used for accepting events from other accounts new EventBusStack(app, "StateMachineEventBusStack", { eventBusName: "StateMachineEventBus", sourceAccounts: process.env.SOURCE_ACCOUNTS, }); // Stack of Lambda functions to notify events of AWS Step Functions CI/CD const noticeSfnCicdEventsFunctionStack = new NoticeSfnCicdEventsFunctionStack( app, "NoticeSfnCicdEventsFunctionStack" ); // Stack of Lambda functions to support the creation of AWS Step Functions workflows new WorkflowSupportFunctionStack(app, "WorkflowSupportFunctionStack"); // Stack for CI/CD of AWS Step Functions // To create multiple StateMachine, duplicate this stack new CicdStack(app, "StateMachineTest001CicdStack", { stateMachineName: "StateMachineTest001", artifactBucket: artifactBucketStack.artifactBucket, sfnTemplateBucket: sfnTemplateBucketStack.sfnTemplateBucket, gitTemplateFileName: "git-template.zip", samTemplateFileName: "sam-template.yml", appTeamWebhookUrl: appTeamWebhookUrl, appTeamManagerWebhookUrl: appTeamManagerWebhookUrl, infraTeamWebhookUrl: infraTeamWebhookUrl, mainBranchApprovalRuleTemplate: roleStack.mainBranchApprovalRuleTemplate, developBranchApprovalRuleTemplate: roleStack.developBranchApprovalRuleTemplate, noticePullRequestEventsFunction: noticeSfnCicdEventsFunctionStack.noticePullRequestEventsFunction, noticeCodeBuildEventsFunction: noticeSfnCicdEventsFunctionStack.noticeCodeBuildEventsFunction, noticeExecuteStateMachineEventsFunction: noticeSfnCicdEventsFunctionStack.noticeExecuteStateMachineEventsFunction, }); // EC2 instances are used to hit the EC2 API in the state machine // Not used in production operations new Ec2InstancesStack(app, "DemoEc2InstancesStack");
準備が出来たらnpx cdk deploy --all
で全てのスタックをデプロイします。全てのスタックをデプロイするには約20分かかります。
# 全てのスタックをデプロイ $ npx cdk deploy --all
developブランチとfeatureブランチの作成
次に、アプリチームは作成されたCodeCommitリポジトリで、develop
ブランチとfeature
ブランチを作成します。
CodeCommitのコンソールより、ブランチ
- ブランチの作成
をクリックします。
ブランチ名をdevelop
、ブランチ元をmain
と入力・選択し、ブランチの作成
をクリックして、develop
ブランチを作成します。
develop
ブランチが作成されたことを確認します。
同様の手順でfeature
ブランチを作成します。
AWS Step Functions Workflow Studioでステートマシンのワークフローを設計
次に、AWS Step Functions Workflow Studioでステートマシンのワークフローを設計します。
Step Functionsのコンソールより、ステートマシン
- ステートマシンの作成
をクリックします。
ワークフローを視覚的に設計
を選択して、次へ
をクリックします。
AWS Step Functions Workflow Studioでステートマシンのワークフローを設計します。設計後は定義
をクリックし、生成された定義(ASL形式)を控えておきます。
今回の例では、Ec2Instance0
を停止させ、5秒待機した後にEc2Instance1
を停止するワークフローを作成しました。
featureブランチに設定ファイルをpush
次に、アプリチームはステートマシン作成に必要な設定ファイルをfeatureブランチにpushします。
AWSマネージメントコンソールからファイルを直接編集する場合は、CodeCommitのコンソールより、対象のリポジトリを選択し、feature
ブランチのファイル名をクリックします。
まず、ASL形式で記述されたステートマシンのワークフローの設定をします。
StateMachineWorkFlow.asl.json
を開き、編集
をクリックします。
事前に控えていたASL形式で記述されたステートマシンのワークフローを貼り付け、作者名、Eメールアドレスを入力し、変更のコミット
をクリックします。
次に、ステートマシンが起動するためのCron式やイベントパターンの指定をします。
StateMachineSettings.yml
を開き、編集
をクリックします。
各種設定情報、作者名、Eメールアドレスを入力し、変更のコミット
をクリックします。今回は以下のような設定をしました。
- 毎日日本時間14:15にステートマシンを実行
- AWS X-Ray でトレースを有効化
- 2つのEC2インスタンスを停止させるIAMポリシーのドキュメント
今回は割愛しますが、どのようなステートマシンなのか分かるようにREADME.md
も編集します。
featureブランチからdevelopブランチへのPull Requestの作成
次に、アプリチームはfeatureブランチからdevelopブランチへのPull Requestを作成します。
プルリクエストの作成
をクリックします。
ターゲットをdevelop
、ソースをfeature
のPull Requestを作成します。タイトルや説明、差分を確認してプルリクエストの作成
をクリックします。
Pull Requestが作成されるとアプリチームのマネージャー宛にSlackで通知が来ます。
Pull Requestの承認とdevelopブランチへのマージ
次に、アプリチームのマネージャーはPull Requestの内容を確認し、承認及び、develop
ブランチにマージします。
通知に記載のURLをクリックし、作成されたPull Requestを確認します。Pull Requestに何かコメントがあればコメントを追加することも出来ます。
Pull Requestへコメントがあると、アプリチーム宛にSlackで通知が来ます。
Pull Requestを確認し、問題がなければ、承認
をクリックします。
続いて、マージをするためマージ
をクリックします。
適当なマージ戦略を選択し、プルリクエストのマージ
をクリックします。
マージされたことを確認します。
developブランチからmainブランチへのPull Requestの作成
次に、アプリチームのマネージャーはdevelop
ブランチからmain
ブランチへのPull Requestを作成します。
手順はfeature
ブランチからdevelop
ブランチへのPull Requestの作成時とターゲットブランチ、ソースブランチが異なる以外は同じなので割愛します。
Pull Requestの承認とmainブランチへのマージ
次に、インフラチームはPull Requestの内容を確認し、承認及び、main
ブランチにマージします。
手順はfeature
ブランチからdevelop
ブランチへのPull Requestの作成時とターゲットブランチ、ソースブランチが異なる以外は同じなので割愛します。
こちらの手順もdevelop
ブランチマージとほぼ同じなので割愛します。
マージが完了すると、以下のようになります。
ステートマシンのデプロイ確認
main
ブランチが更新されると、CodeBuildが動作し、AWS SAMでステートマシンやEventBridgeルールなど各種リソースがデプロイされます。
CodeBuildが動作し始めると、各チャンネルに以下のようにCodeBuildのステータス変更通知が来ます。
通知に記載のリンクをクリックすると、CodeBuildの実行中のビルドを表示します。
CloudFormationのコンソールから、こちらのスタックを確認すると、確かに各種リソースがデプロイされています。
無事各種リソースのデプロイが完了すると、各チャンネルに以下のようにCodeBuildのステータス変更通知が来ます。
CloudFormationのコンソールから、こちらのスタックのステータスを確認すると、CREATE_COMPLETE
となっています。
デプロイされたリソースもリソース
タブから確認できます。
ステートマシンも意図した通りに設定されています。
ステートマシンの動作確認
最後にステートマシンの動作確認です。
Cronで毎日日本時間14:15にステートマシンを実行するようにしていたので、14:15過ぎまで待ってみました。すると、以下のようにステートマシンの開始と終了通知が各チャンネルに来ました。
通知に記載のリンクをクリックすると、実行されたステートマシンを確認することが出来ます。
AWS X-Ray でトレースをカッコよく表示されています。
もちろんEC2インスタンスも2台とも停止されていました。
本気を出せばAWS CDKで大体のことは出来る
AWS CDKでAWS Step FunctionsのCI/CD環境を作ってみました。
向き不向きは置いといて、本気を出せばAWS CDKで大体のことは出来ることが分かりました。
ただ、実際の運用でAWS CDKの環境をメンテナンスすることは中々難しいこともあるので、皆さんは無理しすぎないようにしてください。
偉大な先人も「「IaCで全てが上手くいくと思うなよ!」と言っています。
この記事が誰かの助けになれば幸いです。
以上、AWS事業本部 コンサルティング部の のんピ(@non____97)でした!